#include "OpenGL.h"
#include "Light.h"
#include "Mouse.h"
#include "Keyboard.h"
#include "Texture.h"
#include "DynamicSphere.h"

int OpenGL::width;
int OpenGL::height;
unsigned int OpenGL::mapIndex;
unsigned int OpenGL::sphereIndex;
unsigned int OpenGL::skyBoxIndex;
Camera OpenGL::camera;
list<Boid*>* OpenGL::spheres;

Camera& OpenGL::GetCamera(void)
{
	return camera;
}
void OpenGL::SetCamera(Camera &camera)
{
	camera = camera;
}

unsigned int OpenGL::PrepareSkyBox(void)
{
	unsigned int index = glGenLists(1);
	glNewList(index, GL_COMPILE);
	glBindTexture(GL_TEXTURE_2D, Texture::SkyBox());
	glColor3f(1, 1, 1);
	glBegin(GL_QUADS);

	glTexCoord2f(0, 1.0f / 3.0f);
	glVertex3f(-1, -1, -1);
	glTexCoord2f(0.25f, 1.0f / 3.0f);
	glVertex3f(1, -1, -1);
	glTexCoord2f(0.25f, 2.0f / 3.0f);
	glVertex3f(1, 1, -1);
	glTexCoord2f(0, 2.0f / 3.0f);
	glVertex3f(-1, 1, -1);

	glTexCoord2f(0.5f, 1.0f / 3.0f);
	glVertex3f(1, -1, 1);
	glTexCoord2f(0.75f, 1.0f / 3.0f);
	glVertex3f(-1, -1, 1);
	glTexCoord2f(0.75f, 2.0f / 3.0f);
	glVertex3f(-1, 1, 1);
	glTexCoord2f(0.5f, 2.0f / 3.0f);
	glVertex3f(1, 1, 1);

	glTexCoord2f(0.25f, 1.0f / 3.0f);
	glVertex3f(1, -1, -1);
	glTexCoord2f(0.5f, 1.0f / 3.0f);
	glVertex3f(1, -1, 1);
	glTexCoord2f(0.5f, 2.0f / 3.0f);
	glVertex3f(1, 1, 1);
	glTexCoord2f(0.25f, 2.0f / 3.0f);
	glVertex3f(1, 1, -1);

	glTexCoord2f(0.75f, 1.0f / 3.0f);
	glVertex3f(-1, -1, 1);
	glTexCoord2f(1.0f, 1.0f / 3.0f);
	glVertex3f(-1, -1, -1);
	glTexCoord2f(1.0f, 2.0f / 3.0f);
	glVertex3f(-1, 1, -1);
	glTexCoord2f(0.75f, 2.0f / 3.0f);
	glVertex3f(-1, 1, 1);

	glTexCoord2f(0, 2.0f / 3.0f);
	glVertex3f(-1, 1, -1);
	glTexCoord2f(0.25f, 2.0f / 3.0f);
	glVertex3f(1, 1, -1);
	glTexCoord2f(0.25f, 1.0f);
	glVertex3f(1, 1, 1);
	glTexCoord2f(0, 1.0f);
	glVertex3f(-1, 1, 1);

	glTexCoord2f(0, 0.0f);
	glVertex3f(-1, -1, 1);
	glTexCoord2f(0.25f, 0.0f);
	glVertex3f(1, -1, 1);
	glTexCoord2f(0.25f, 1.0f / 3.0f);
	glVertex3f(1, -1, -1);
	glTexCoord2f(0, 1.0f / 3.0f);
	glVertex3f(-1, -1, -1);

	glEnd();
	glEndList();
	return index;
}

unsigned int OpenGL::PrepareSphere(float radius)
{
	sphereIndex = glGenLists(1);
	GLUquadricObj *quadric = gluNewQuadric();
	gluQuadricDrawStyle(quadric, GLU_FILL);
	gluQuadricNormals(quadric, GLU_SMOOTH);
	gluQuadricOrientation(quadric, GLU_OUTSIDE);
	gluQuadricTexture(quadric, GLU_TRUE);

	glNewList(sphereIndex, GL_COMPILE);
	gluSphere(quadric, radius, 20, 10);
	glEndList();
	gluDeleteQuadric(quadric);
	return sphereIndex;
}

void OpenGL::Resize(int width, int height, bool changeTexture)		// Resize And Initialize The GL Window
{
	if (height == 0)										// Prevent A Divide By Zero By	
		height = 1;										// Making Height Equal One	

	OpenGL::width = width;
	OpenGL::height = height;

	glViewport(0, 0, width, height);						// Reset The Current Viewport

	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
	glLoadIdentity();									// Reset The Projection Matrix	
	gluPerspective(60.0f,(GLfloat)width/(GLfloat)height,0.01f,100.0f);	
	glMatrixMode(GL_MODELVIEW);							// Select The Modelview Matrix
	glLoadIdentity();									// Reset The Modelview Matrix	
}

void OpenGL::Init(int width, int height, Map& map, list<Boid*>* spheres)										// All Setup For OpenGL Goes Here
{
	Resize(width, height);
	glShadeModel(GL_SMOOTH);							// Enable Smooth Shading
	glClearColor(0.0f, 0.0f, 0.0f, 0.5f);				// Black Background
	glClearStencil(0);
	glClearDepth(1.0f);									// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST);							// Enables Depth Testing
	glDepthFunc(GL_LEQUAL);								// The Type Of Depth Testing To Do
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);	// Really Nice Perspective Calculations
	glFrontFace(GL_CCW);
	glEnable(GL_CULL_FACE);
	glCullFace(GL_BACK);
	glPointSize(5.0f);
	glEnable(GL_LIGHTING);
	glEnable(GL_COLOR_MATERIAL);
	glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
	glEnable(GL_NORMALIZE);

	Light::Init((float)map.Columns(), map.GetHighest(), (float)map.Rows());
	Light::LightsOn();

	Texture::Load();
	mapIndex = PrepareMap(map);
	sphereIndex = PrepareSphere(DynamicSphere::Radius());
	skyBoxIndex = PrepareSkyBox();
	camera.MoveZ(10);
	camera.MoveY(4);

	OpenGL::spheres = spheres;		
}

unsigned int OpenGL::PrepareMap(Map& map)
{
	unsigned int index = glGenLists(1);
	glNewList(index, GL_COMPILE);

	//Buildings
	const int PIECES = 1;
	int i, j, x, y;
	glColor3f(1, 1, 1);
	glBindTexture(GL_TEXTURE_2D, Texture::Building());
	glBegin(GL_QUADS);	
	for(y = 0; y < map.Rows(); y++)
	{
		for(x = 0; x < map.Columns(); x++)
		{			
			for(i = 0; i < PIECES; i++)
			{
				for(j = 0; j < PIECES; j++)
				{
					//Front
					glNormal3f(0.0f, 0.0f, 1.0f);
					glTexCoord2f((float)i / PIECES, map.Field(x, y).Height() * (float)j / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)i / PIECES,
								map.Field(x, y).Height() * (float)j / PIECES,
								y + 0.5f + 0.5f * Box::Width());					
					glTexCoord2f((float)(i + 1) / PIECES, map.Field(x, y).Height() * (float)j / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)(i + 1) / PIECES, 
								map.Field(x, y).Height() * (float)j / PIECES, 
								y + 0.5f + 0.5f * Box::Width());
					glTexCoord2f((float)(i + 1) / PIECES, map.Field(x, y).Height() * (float)(j + 1) / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)(i + 1) / PIECES, 
								map.Field(x, y).Height() * (float)(j + 1) / PIECES, 
								y + 0.5f + 0.5f * Box::Width());
					glTexCoord2f((float)i / PIECES, map.Field(x, y).Height() * (float)(j + 1) / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)i / PIECES, 
								map.Field(x, y).Height() * (float)(j + 1) / PIECES, 
								y + 0.5f + 0.5f * Box::Width());

					//Back
					glNormal3f(0.0f, 0.0f, -1.0f);
					glTexCoord2f((float)(i + 1) / PIECES, map.Field(x, y).Height() * (float)j / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)(i + 1) / PIECES, 
								map.Field(x, y).Height() * (float)j / PIECES, 
								y + 0.5f - 0.5f * Box::Width());
					glTexCoord2f((float)i / PIECES, map.Field(x, y).Height() * (float)j / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)i / PIECES,
								map.Field(x, y).Height() * (float)j / PIECES,
								y + 0.5f - 0.5f * Box::Width());
					glTexCoord2f((float)i / PIECES, map.Field(x, y).Height() * (float)(j + 1) / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)i / PIECES, 
								map.Field(x, y).Height() * (float)(j + 1) / PIECES, 
								y + 0.5f - 0.5f * Box::Width());
					glTexCoord2f((float)(i + 1) / PIECES, map.Field(x, y).Height() * (float)(j + 1) / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)(i + 1) / PIECES, 
								map.Field(x, y).Height() * (float)(j + 1) / PIECES, 
								y + 0.5f - 0.5f * Box::Width());

					//Left
					glNormal3f(-1.0f, 0.0f, 0.0f);
					glTexCoord2f((float)i / PIECES, map.Field(x, y).Height() * (float)j / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width(),
								map.Field(x, y).Height() * (float)j / PIECES,
								y + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)i / PIECES);					
					glTexCoord2f((float)(i + 1) / PIECES, map.Field(x, y).Height() * (float)j / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width(), 
								map.Field(x, y).Height() * (float)j / PIECES, 
								y + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)(i + 1) / PIECES);
					glTexCoord2f((float)(i + 1) / PIECES, map.Field(x, y).Height() * (float)(j + 1) / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width(), 
								map.Field(x, y).Height() * (float)(j + 1) / PIECES, 
								y + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)(i + 1) / PIECES);
					glTexCoord2f((float)i / PIECES, map.Field(x, y).Height() * (float)(j + 1) / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width(), 
								map.Field(x, y).Height() * (float)(j + 1) / PIECES, 
								y + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)i / PIECES);

					//Right
					glNormal3f(1.0f, 0.0f, 0.0f);
					glTexCoord2f((float)(i + 1) / PIECES, map.Field(x, y).Height() * (float)j / PIECES);
					glVertex3f(	x + 0.5f + 0.5f * Box::Width(), 
								map.Field(x, y).Height() * (float)j / PIECES, 
								y + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)(i + 1) / PIECES);
					glTexCoord2f((float)i / PIECES, map.Field(x, y).Height() * (float)j / PIECES);
					glVertex3f(	x + 0.5f + 0.5f * Box::Width(),
								map.Field(x, y).Height() * (float)j / PIECES,
								y + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)i / PIECES);					
					glTexCoord2f((float)i / PIECES, map.Field(x, y).Height() * (float)(j + 1) / PIECES);
					glVertex3f(	x + 0.5f + 0.5f * Box::Width(), 
								map.Field(x, y).Height() * (float)(j + 1) / PIECES, 
								y + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)i / PIECES);
					glTexCoord2f((float)(i + 1) / PIECES, map.Field(x, y).Height() * (float)(j + 1) / PIECES);
					glVertex3f(	x + 0.5f + 0.5f * Box::Width(), 
								map.Field(x, y).Height() * (float)(j + 1) / PIECES, 
								y + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)(i + 1) / PIECES);
					
					//Top
					glNormal3f(0.0f, 1.0f, 0.0f);
					glTexCoord2f((float)(i + 1) / PIECES, (float)(j + 1) / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)(i + 1) / PIECES, 
								map.Field(x, y).Height(),
								y + 0.5f + 0.5f * Box::Width() - Box::Width() * (float)(j + 1) / PIECES);
					glTexCoord2f((float)i / PIECES, (float)(j + 1) / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)i / PIECES,
								map.Field(x, y).Height(),
								y + 0.5f + 0.5f * Box::Width() - Box::Width() * (float)(j + 1) / PIECES);					
					glTexCoord2f((float)i / PIECES, (float)j / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)i / PIECES, 
								map.Field(x, y).Height(),
								y + 0.5f + 0.5f * Box::Width() - Box::Width() * (float)j / PIECES);
					glTexCoord2f((float)(i + 1) / PIECES, (float)j / PIECES);
					glVertex3f(	x + 0.5f - 0.5f * Box::Width() + Box::Width() * (float)(i + 1) / PIECES, 
								map.Field(x, y).Height(),
								y + 0.5f + 0.5f * Box::Width() - Box::Width() * (float)j / PIECES);					
				}
			}
		}
	}	
	glEnd();

	//Land
	glBindTexture(GL_TEXTURE_2D, Texture::Ground());
	glBegin(GL_QUADS);
	const int LAND_PIECES = 10;	
	glColor3f(1.0f, 1.0f, 1.0f);	
	float stretchX = (float)map.Columns();
	float stretchY = (float)map.Rows();
	for(i = 0; i < LAND_PIECES; i++)
	{		
		for(j = 0; j < LAND_PIECES; j++)
		{									
			glNormal3f(0.0f, 1.0f, 0.0f);
			glTexCoord2f(stretchX * (float)(i + 1) / LAND_PIECES, stretchY * (float)(j + 1) / LAND_PIECES);
			glVertex3f(	-10.0f + (20.0f + map.Columns()) * (float)(i + 1) / LAND_PIECES,
						0,
						10.0f + map.Rows() - (20.0f + map.Rows()) * (float)(j + 1) / LAND_PIECES);
			glTexCoord2f(stretchX * (float)i / LAND_PIECES, stretchY * (float)(j + 1) / LAND_PIECES);
			glVertex3f(	-10.0f + (20.0f + map.Columns()) * (float)i / LAND_PIECES,
						0,
						10.0f + map.Rows() - (20.0f + map.Rows()) * (float)(j + 1) / LAND_PIECES);
			glTexCoord2f(stretchX * (float)i / LAND_PIECES, stretchY * (float)j / LAND_PIECES);
			glVertex3f(	-10.0f + (20.0f + map.Columns()) * (float)i / LAND_PIECES,
						0,
						10.0f + map.Rows() - (20.0f + map.Rows()) * (float)j / LAND_PIECES);
			glTexCoord2f(stretchX * (float)(i + 1) / LAND_PIECES, stretchY * (float)j / LAND_PIECES);
			glVertex3f(	-10.0f + (20.0f + map.Columns()) * (float)(i + 1) / LAND_PIECES,
						0,
						10.0f + map.Rows() - (20.0f + map.Rows()) * (float)j / LAND_PIECES);			
		}
	}

	glEnd();
	glEndList();
	return index;
}

void OpenGL::UpdateCamera(float time)
{
	//rotation
	static bool lastRightDown = false;
	static int lastX = 0, lastY = 0;
	
	int dx = Mouse::X() - (width >> 1);
	int dy = Mouse::Y() - (height >> 1);
	Mouse::SetPosition(width >> 1, height >> 1);	
	camera.RotateY(0.003f * dx);
	camera.RotateX(0.003f * dy);
	
	//Move
	float speed = 2.0f;
	if(Keyboard::KeyPressed(VK_SHIFT))
		speed *= 5.0f;
	float distance = speed * time;
	if(Keyboard::KeyPressed('W'))	
		OpenGL::camera.MoveZ(-distance);	
	if(Keyboard::KeyPressed('S'))	
		OpenGL::camera.MoveZ(distance);	
	if(Keyboard::KeyPressed('A'))	
		OpenGL::camera.MoveX(-distance);	
	if(Keyboard::KeyPressed('D'))	
		OpenGL::camera.MoveX(distance);	
	if(Keyboard::KeyPressed('E'))	
		OpenGL::camera.MoveY(distance);	
	if(Keyboard::KeyPressed('Q'))	
		OpenGL::camera.MoveY(-distance);	
}

void OpenGL::Render(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	// Clear Screen And Depth Buffer
	glLoadIdentity();			
			
	Light::GetLight(4)->SetPosition(camera.Position());
	camera.Update();
	Light::UpdateAll();	
	//Render Sky Box
	glEnable(GL_TEXTURE_2D);
	glPushMatrix();
	glTranslatef(camera.Position().x, camera.Position().y, camera.Position().z);
	glDisable(GL_LIGHTING);
	glCallList(skyBoxIndex);
	glClear(GL_DEPTH_BUFFER_BIT);
	glEnable(GL_LIGHTING);
	glPopMatrix();	

	//Render map	
	glCallList(mapIndex);
	glDisable(GL_TEXTURE_2D);

	//RenderSpheres	
	glColor3f(1, 1, 0);
	Vector3 position;
	list<Boid*>::iterator sphere;
	for(sphere = spheres->begin(); sphere != spheres->end(); ++sphere)
	{
		glPushMatrix();
		position = (*sphere)->Position();
		if((*sphere)->Type() == Boid::BoidType::Red)
			glColor3f(1, 0, 0);
		else
			glColor3f(1, 1, 0);
		glTranslatef(position.x, position.y, position.z);
		glCallList(sphereIndex);
		glPopMatrix();
	}	
		
	//Render sights
	glClear(GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	glDisable(GL_LIGHTING);
	glBegin(GL_POINTS);
	glColor3f(1, 0, 0);
	glVertex3f(0, 0, -1);		
	glEnd();

	glFlush();		
}

void OpenGL::Destroy(void)
{	
	glDeleteLists(mapIndex, 1);
	Light::Clear();
	Texture::Delete();
}
